{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "import os, shutil, random, glob\n",
    "import bcolz\n",
    "import keras\n",
    "import keras.preprocessing.image\n",
    "from keras.layers import Input, Flatten, Dense, Dropout, Activation, BatchNormalization, GlobalMaxPooling2D\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "from keras.optimizers import Adam\n",
    "from keras.applications.vgg19 import preprocess_input\n",
    "from keras.applications.vgg19 import VGG19\n",
    "from keras.models import Model\n",
    "%matplotlib inline\n",
    "from matplotlib import pyplot as plt\n",
    "import numpy as np\n",
    "import scipy\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Code below assumes that the train data from the https://www.kaggle.com/c/dogs-vs-cats competition has been downloaded and unzipped into the `train` directory under root of the repository."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "files = glob.glob('train/*')\n",
    "fnames = [f.split('/')[1] for f in files]\n",
    "\n",
    "os.makedirs('train/cats')\n",
    "os.makedirs('train/dogs')\n",
    "\n",
    "for fname in fnames:\n",
    "    dogs_or_cats = 'dogs' if 'dog' in fname else 'cats'\n",
    "    shutil.move(f'train/{fname}', f'train/{dogs_or_cats}/{fname}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 25000 images belonging to 2 classes.\n"
     ]
    }
   ],
   "source": [
    "gen = ImageDataGenerator(preprocessing_function=preprocess_input)\n",
    "train_data = gen.flow_from_directory('train', target_size=(224, 224), batch_size=1, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "train_filenames = train_data.filenames\n",
    "bcolz.carray(train_filenames, rootdir='train_filenames', mode='w').flush()\n",
    "train_y = keras.utils.to_categorical(train_data.classes)\n",
    "bcolz.carray(train_y, rootdir='train_y', mode='w').flush()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "base_model = VGG19(\n",
    "    include_top=False,\n",
    "    weights='imagenet',\n",
    "    input_shape=(224, 224, 3),\n",
    "    pooling=None\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "train_X = base_model.predict_generator(train_data, steps=train_data.n)\n",
    "bcolz.carray(train_X, rootdir='train_X', mode='w').flush()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "trn_ids = np.random.randint(25000, size=6)\n",
    "val_ids = np.delete(np.arange(25000), trn_ids)\n",
    "\n",
    "trn_X = train_X[trn_ids, ...]\n",
    "trn_y = train_y[trn_ids]\n",
    "\n",
    "random_subset = np.random.randint(24994, size=500)\n",
    "val_X = train_X[random_subset, ...]\n",
    "val_y = train_y[random_subset]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "inputs = Input(shape=(7, 7, 512))\n",
    "# x = keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2))(inputs)\n",
    "# x = Flatten()(x)\n",
    "# x = Dense(4096)(x)\n",
    "\n",
    "x = GlobalMaxPooling2D()(inputs)\n",
    "x = Dense(4096)(x)\n",
    "x = BatchNormalization()(x)\n",
    "x = Activation('relu')(x)\n",
    "x = Dense(2)(x)\n",
    "x = BatchNormalization()(x)\n",
    "predictions = Activation('softmax')(x)\n",
    "\n",
    "model = Model(inputs, predictions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_2 (InputLayer)         (None, 7, 7, 512)         0         \n",
      "_________________________________________________________________\n",
      "global_max_pooling2d_1 (Glob (None, 512)               0         \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 4096)              2101248   \n",
      "_________________________________________________________________\n",
      "batch_normalization_1 (Batch (None, 4096)              16384     \n",
      "_________________________________________________________________\n",
      "activation_1 (Activation)    (None, 4096)              0         \n",
      "_________________________________________________________________\n",
      "dense_2 (Dense)              (None, 2)                 8194      \n",
      "_________________________________________________________________\n",
      "batch_normalization_2 (Batch (None, 2)                 8         \n",
      "_________________________________________________________________\n",
      "activation_2 (Activation)    (None, 2)                 0         \n",
      "=================================================================\n",
      "Total params: 2,125,834\n",
      "Trainable params: 2,117,638\n",
      "Non-trainable params: 8,196\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "model.compile(Adam(lr=1e-4), 'categorical_crossentropy', metrics=['accuracy'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 6 samples, validate on 500 samples\n",
      "Epoch 1/40\n",
      "13s - loss: 1.3000 - acc: 0.1667 - val_loss: 3.6826 - val_acc: 0.5820\n",
      "Epoch 2/40\n",
      "0s - loss: 0.3923 - acc: 1.0000 - val_loss: 2.5259 - val_acc: 0.6480\n",
      "Epoch 3/40\n",
      "0s - loss: 0.2874 - acc: 1.0000 - val_loss: 1.9335 - val_acc: 0.7020\n",
      "Epoch 4/40\n",
      "0s - loss: 0.2282 - acc: 1.0000 - val_loss: 1.5719 - val_acc: 0.7280\n",
      "Epoch 5/40\n",
      "0s - loss: 0.1965 - acc: 1.0000 - val_loss: 1.3259 - val_acc: 0.7400\n",
      "Epoch 6/40\n",
      "0s - loss: 0.1788 - acc: 1.0000 - val_loss: 1.1501 - val_acc: 0.7540\n",
      "Epoch 7/40\n",
      "0s - loss: 0.1686 - acc: 1.0000 - val_loss: 1.0168 - val_acc: 0.7640\n",
      "Epoch 8/40\n",
      "0s - loss: 0.1625 - acc: 1.0000 - val_loss: 0.9130 - val_acc: 0.7720\n",
      "Epoch 9/40\n",
      "0s - loss: 0.1587 - acc: 1.0000 - val_loss: 0.8284 - val_acc: 0.7800\n",
      "Epoch 10/40\n",
      "0s - loss: 0.1562 - acc: 1.0000 - val_loss: 0.7584 - val_acc: 0.7860\n",
      "Epoch 11/40\n",
      "0s - loss: 0.1544 - acc: 1.0000 - val_loss: 0.7007 - val_acc: 0.7920\n",
      "Epoch 12/40\n",
      "0s - loss: 0.1531 - acc: 1.0000 - val_loss: 0.6527 - val_acc: 0.7980\n",
      "Epoch 13/40\n",
      "0s - loss: 0.1521 - acc: 1.0000 - val_loss: 0.6126 - val_acc: 0.8000\n",
      "Epoch 14/40\n",
      "0s - loss: 0.1512 - acc: 1.0000 - val_loss: 0.5793 - val_acc: 0.8000\n",
      "Epoch 15/40\n",
      "0s - loss: 0.1504 - acc: 1.0000 - val_loss: 0.5514 - val_acc: 0.8040\n",
      "Epoch 16/40\n",
      "0s - loss: 0.1496 - acc: 1.0000 - val_loss: 0.5282 - val_acc: 0.8040\n",
      "Epoch 17/40\n",
      "0s - loss: 0.1488 - acc: 1.0000 - val_loss: 0.5088 - val_acc: 0.8100\n",
      "Epoch 18/40\n",
      "0s - loss: 0.1480 - acc: 1.0000 - val_loss: 0.4927 - val_acc: 0.8100\n",
      "Epoch 19/40\n",
      "0s - loss: 0.1472 - acc: 1.0000 - val_loss: 0.4793 - val_acc: 0.8100\n",
      "Epoch 20/40\n",
      "0s - loss: 0.1464 - acc: 1.0000 - val_loss: 0.4682 - val_acc: 0.8120\n",
      "Epoch 21/40\n",
      "0s - loss: 0.1456 - acc: 1.0000 - val_loss: 0.4591 - val_acc: 0.8140\n",
      "Epoch 22/40\n",
      "0s - loss: 0.1448 - acc: 1.0000 - val_loss: 0.4516 - val_acc: 0.8140\n",
      "Epoch 23/40\n",
      "0s - loss: 0.1441 - acc: 1.0000 - val_loss: 0.4455 - val_acc: 0.8140\n",
      "Epoch 24/40\n",
      "0s - loss: 0.1433 - acc: 1.0000 - val_loss: 0.4405 - val_acc: 0.8140\n",
      "Epoch 25/40\n",
      "0s - loss: 0.1425 - acc: 1.0000 - val_loss: 0.4366 - val_acc: 0.8140\n",
      "Epoch 26/40\n",
      "0s - loss: 0.1418 - acc: 1.0000 - val_loss: 0.4336 - val_acc: 0.8140\n",
      "Epoch 27/40\n",
      "0s - loss: 0.1411 - acc: 1.0000 - val_loss: 0.4312 - val_acc: 0.8120\n",
      "Epoch 28/40\n",
      "0s - loss: 0.1404 - acc: 1.0000 - val_loss: 0.4295 - val_acc: 0.8100\n",
      "Epoch 29/40\n",
      "0s - loss: 0.1397 - acc: 1.0000 - val_loss: 0.4283 - val_acc: 0.8100\n",
      "Epoch 30/40\n",
      "0s - loss: 0.1391 - acc: 1.0000 - val_loss: 0.4275 - val_acc: 0.8100\n",
      "Epoch 31/40\n",
      "0s - loss: 0.1385 - acc: 1.0000 - val_loss: 0.4271 - val_acc: 0.8100\n",
      "Epoch 32/40\n",
      "0s - loss: 0.1379 - acc: 1.0000 - val_loss: 0.4270 - val_acc: 0.8100\n",
      "Epoch 33/40\n",
      "0s - loss: 0.1374 - acc: 1.0000 - val_loss: 0.4270 - val_acc: 0.8060\n",
      "Epoch 34/40\n",
      "0s - loss: 0.1369 - acc: 1.0000 - val_loss: 0.4273 - val_acc: 0.8060\n",
      "Epoch 35/40\n",
      "0s - loss: 0.1364 - acc: 1.0000 - val_loss: 0.4277 - val_acc: 0.8040\n",
      "Epoch 36/40\n",
      "0s - loss: 0.1359 - acc: 1.0000 - val_loss: 0.4283 - val_acc: 0.8020\n",
      "Epoch 37/40\n",
      "0s - loss: 0.1355 - acc: 1.0000 - val_loss: 0.4289 - val_acc: 0.8020\n",
      "Epoch 38/40\n",
      "0s - loss: 0.1351 - acc: 1.0000 - val_loss: 0.4297 - val_acc: 0.8000\n",
      "Epoch 39/40\n",
      "0s - loss: 0.1347 - acc: 1.0000 - val_loss: 0.4305 - val_acc: 0.8000\n",
      "Epoch 40/40\n",
      "0s - loss: 0.1343 - acc: 1.0000 - val_loss: 0.4313 - val_acc: 0.8000\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<keras.callbacks.History at 0x7fdb1020b208>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.fit(x=trn_X, y=trn_y, batch_size=6, epochs=40, validation_data=(val_X, val_y), verbose=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's validate on the entire training set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "val_X = train_X[val_ids, ...]\n",
    "val_y = train_y[val_ids]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 6 samples, validate on 24994 samples\n",
      "Epoch 1/1\n",
      "10s - loss: 0.1340 - acc: 1.0000 - val_loss: 0.4262 - val_acc: 0.8204\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<keras.callbacks.History at 0x7fdaf369de80>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.fit(x=trn_X, y=trn_y, batch_size=6, epochs=1, validation_data=(val_X, val_y), verbose=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['dogs/dog.9455.jpg',\n",
       " 'cats/cat.4549.jpg',\n",
       " 'cats/cat.10649.jpg',\n",
       " 'dogs/dog.1881.jpg',\n",
       " 'dogs/dog.4863.jpg',\n",
       " 'cats/cat.9190.jpg']"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[train_filenames[idx] for idx in trn_ids]"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
